You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
101 lines
3.0 KiB
101 lines
3.0 KiB
<script setup lang="ts">
|
|
import { unwrapApiBody, type ApiResponse } from '../../../../utils/http/factory'
|
|
import { renderSafeMarkdown } from '../../../../utils/render-markdown'
|
|
import { formatOccurredOnDisplay, occurredOnToIsoAttr } from '../../../../utils/timeline-datetime'
|
|
|
|
const route = useRoute()
|
|
const requestFetch = useRequestFetch()
|
|
const id = computed(() => route.params.id as string)
|
|
|
|
type Post = {
|
|
id: number
|
|
title: string
|
|
slug: string
|
|
excerpt: string
|
|
bodyMarkdown: string
|
|
coverUrl: string | null
|
|
publishedAt: Date | null
|
|
visibility: 'private' | 'unlisted' | 'public'
|
|
shareToken?: string | null
|
|
}
|
|
|
|
const { data, pending, error } = await useAsyncData(
|
|
() => `me-post-preview-${id.value}`,
|
|
async () => {
|
|
const res = await requestFetch<ApiResponse<{ post: Post }>>(`/api/me/posts/${encodeURIComponent(id.value)}`)
|
|
return unwrapApiBody(res).post
|
|
},
|
|
{ watch: [id] },
|
|
)
|
|
|
|
const renderedBody = computed(() =>
|
|
data.value ? renderSafeMarkdown(data.value.bodyMarkdown) : '',
|
|
)
|
|
|
|
const publishedAtLabel = computed(() =>
|
|
data.value?.publishedAt != null ? formatOccurredOnDisplay(data.value.publishedAt) : '',
|
|
)
|
|
|
|
const publishedAtIso = computed(() =>
|
|
data.value?.publishedAt != null ? occurredOnToIsoAttr(data.value.publishedAt) : '',
|
|
)
|
|
|
|
const visibilityLabel = computed(() => {
|
|
if (!data.value) {
|
|
return ''
|
|
}
|
|
if (data.value.visibility === 'private') {
|
|
return '私密预览'
|
|
}
|
|
if (data.value.visibility === 'unlisted') {
|
|
return '仅链接预览'
|
|
}
|
|
return '公开预览'
|
|
})
|
|
|
|
usePageTitle(() => {
|
|
const t = data.value?.title?.trim()
|
|
return t ? [t, '预览'] : ['文章预览']
|
|
})
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<UContainer class="py-10 space-y-6">
|
|
<div v-if="pending && !data" class="text-muted">
|
|
加载中…
|
|
</div>
|
|
<UAlert v-else-if="error && !data" color="error" title="文章不存在或无权限查看" />
|
|
<template v-else-if="data">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<UBadge color="neutral" variant="soft">
|
|
{{ visibilityLabel }}
|
|
</UBadge>
|
|
<UButton :to="`/me/posts/${data.id}`" color="neutral" variant="soft" size="sm">
|
|
编辑
|
|
</UButton>
|
|
</div>
|
|
<div v-if="data.coverUrl" class="flex justify-center">
|
|
<img
|
|
:src="data.coverUrl"
|
|
alt=""
|
|
class="max-h-64 w-full max-w-2xl rounded-lg object-cover border border-default"
|
|
>
|
|
</div>
|
|
<p v-if="publishedAtLabel" class="text-sm tabular-nums text-muted">
|
|
发布于
|
|
<time :datetime="publishedAtIso" class="text-default">{{ publishedAtLabel }}</time>
|
|
</p>
|
|
<h1 class="text-2xl font-semibold">
|
|
{{ data.title }}
|
|
</h1>
|
|
<p v-if="data.excerpt" class="text-muted">
|
|
{{ data.excerpt }}
|
|
</p>
|
|
<article
|
|
class="prose prose-neutral dark:prose-invert max-w-none prose-a:text-primary prose-img:rounded-lg prose-headings:text-highlighted prose-p:text-default prose-strong:text-highlighted markdown-body green"
|
|
v-html="renderedBody"
|
|
/>
|
|
</template>
|
|
</UContainer>
|
|
</template>
|
|
|